﻿/*  
	========================================================================
	 awzBoxes Library
	
	 Homepage: http://www.awzhome.de/
	========================================================================
	
	This Source Code Form is subject to the terms of the Mozilla Public
	License, v. 2.0. If a copy of the MPL was not distributed with this
	file, You can obtain one at http://mozilla.org/MPL/2.0/.
   
	The Original Code is code of awzBoxes Library.

	The Initial Developer of the Original Code is Andreas Weizel.
	Portions created by the Initial Developer are
	Copyright (C) 2011-2012 Andreas Weizel. All Rights Reserved.
	
	Contributor(s): (none)
	
	========================================================================
*/

using System.Linq;
using System.Windows.Controls;
using System.Collections.ObjectModel;
using System.Windows.Markup;
using System.Collections.Specialized;
using System.Windows;
using System;
using AWZhome.awzBoxes.Tools;
using System.Collections.Generic;

namespace AWZhome.awzBoxes
{
	/// <summary>
	/// Interaction logic for BoxArea.xaml
	/// </summary>
	[ContentProperty("BoxLines")]
	public partial class BoxArea : UserControl
	{
		private BoxWindowContainer _parentBoxWindowContainer;

		private bool _highlightTopBorder;
		private bool _highlightBottomBorder;
		private bool _highlightLeftBorder;
		private bool _highlightRightBorder;
		private BoxItemDockDirection _direction = BoxItemDockDirection.DockInnerArea;
		private BoxItem _activeBoxItem = null;

		#region Events

		/// <summary>
		/// Event fired when the currently active <see cref="AWZhome.awzBoxes.BoxItem"/>
		/// in the box area changes.
		/// </summary>
		public event EventHandler<ActiveBoxItemChangedEventArgs> ActiveBoxItemChanged;

		#endregion

		public BoxArea()
		{
			this.BoxLines = new ObservableCollection<BoxLine>();
			this.BoxLines.CollectionChanged += BoxLines_CollectionChanged;
			this.AutoHiddenBoxes = new ObservableCollection<AutoHiddenBox>();

			InitializeComponent();
			this.UpdateVisibility();
		}

		/// <summary>
		/// Returns the collection of <see cref="AWZhome.awzBoxes.BoxLine"/> elements
		/// shown in the BoxArea.
		/// </summary>
		public ObservableCollection<BoxLine> BoxLines
		{
			get;
			private set;
		}

		/// <summary>
		/// Returns the collection of <see cref="AWZhome.awzBoxes.AutoHiddenBox"/>
		/// objects in this BoxArea.
		/// </summary>
		public ObservableCollection<AutoHiddenBox> AutoHiddenBoxes
		{
			get;
			private set;
		}

		/// <summary>
		/// Determines whether to highlight the top border of the BoxArea.
		/// </summary>
		internal bool HighlightTopBorder
		{
			get
			{
				return _highlightTopBorder;
			}
			set
			{
				_highlightTopBorder = value;
				Thickness border = this.BorderThickness;
				border.Top = (value) ? 2 : 0;
				this.BorderThickness = border;
			}
		}

		/// <summary>
		/// Determines whether to highlight the bottom border of the BoxArea.
		/// </summary>
		internal bool HighlightBottomBorder
		{
			get
			{
				return _highlightBottomBorder;
			}
			set
			{
				_highlightBottomBorder = value;
				Thickness border = this.BorderThickness;
				border.Bottom = (value) ? 2 : 0;
				this.BorderThickness = border;
			}
		}

		/// <summary>
		/// Determines whether to highlight the left border of the BoxArea.
		/// </summary>
		internal bool HighlightLeftBorder
		{
			get
			{
				return _highlightLeftBorder;
			}
			set
			{
				_highlightLeftBorder = value;
				Thickness border = this.BorderThickness;
				border.Left = (value) ? 2 : 0;
				this.BorderThickness = border;
			}
		}

		/// <summary>
		/// Determines whether to highlight the right border of the BoxArea.
		/// </summary>
		internal bool HighlightRightBorder
		{
			get
			{
				return _highlightRightBorder;
			}
			set
			{
				_highlightRightBorder = value;
				Thickness border = this.BorderThickness;
				border.Right = (value) ? 2 : 0;
				this.BorderThickness = border;
			}
		}

		/// <summary>
		/// Sets the width of a specific row.
		/// </summary>
		/// <param name="index">Index of the grid row.</param>
		/// <param name="height">Row height to set.</param>
		public void SetRowHeight(int index, double height)
		{
			this.splittedGrid.SetRowHeight(index, height);
		}

		/// <summary>
		/// Retrieves the height of a specific row.
		/// </summary>
		/// <param name="index">Index of the grid row.</param>
		/// <param name="actualHeight">If <c>true</c>, returns the actual height of the row.</param>
		/// <returns>The row height.</returns>
		public double GetRowHeight(int index, bool actualHeight)
		{
			return this.splittedGrid.GetRowHeight(index, actualHeight) ?? 0f;
		}

		/// <summary>
		/// Checks whether a <see cref="AWZhome.awzBoxes.BoxItem"/> object can be added
		/// to the BoxArea.
		/// </summary>
		/// <param name="boxItem">BoxItem to check.</param>
		/// <returns><c>True</c> if the BoxItem may be docked here, <c>false</c> otherwise.</returns>
		public bool CanContainBoxItem(BoxItem boxItem)
		{
			return ((boxItem.AllowedDockAreas & this._direction) == this._direction);
		}

		/// <summary>
		/// Determines whether the BoxArea belongs to a side bar.
		/// </summary>
		internal bool IsSideBar
		{
			get;
			set;
		}

		/// <summary>
		/// Gets/sets the active <see cref="AWZhome.awzBoxes.BoxItem"/>
		/// for the current box area.
		/// </summary>
		public BoxItem ActiveBoxItem
		{
			get
			{
				return _activeBoxItem;
			}
			set
			{
				BoxItem oldActiveItem = _activeBoxItem;
				_activeBoxItem = value;
				UInt64 maxActivityCount = 0;
				foreach (BoxLine line in this.BoxLines)
				{
					foreach (Box box in line.Boxes)
					{
						foreach (BoxItem item in box.Items)
						{
							if (maxActivityCount < item.ActivityCount)
							{
								maxActivityCount = item.ActivityCount;
							}
							if (item == value)
							{
								item.IsActive = true;
							}
							else
							{
								item.IsActive = false;
							}
						}
					}
				}

				if (value != null)
				{
					value.ActivityCount = maxActivityCount + 1;
				}

				// Fire the ActiveBoxItemChanged event
				if (this.ActiveBoxItemChanged != null)
				{
					this.ActiveBoxItemChanged(this,
						new ActiveBoxItemChangedEventArgs(oldActiveItem, value));
				}
			}
		}

		/// <summary>
		/// Gets the direction of current box area.
		/// </summary>
		public BoxItemDockDirection Direction
		{
			get
			{
				return _direction;
			}
			internal set
			{
				switch (value)
				{
					case BoxItemDockDirection.NotDocking:
					case BoxItemDockDirection.DockableEverywhere:
						// Invalid value!
						throw new InvalidOperationException("Invalid direction for a BoxArea.");
					default:
						break;
				}

				_direction = value;
				this.IsSideBar = (_direction != BoxItemDockDirection.DockInnerArea);
				this.UpdateVisibility();
			}
		}

		/// <summary>
		/// Retrieves the <see cref="AWZhome.awzBoxes.ParentBoxWindowContainer"/> that contains the current box area.
		/// </summary>
		public BoxWindowContainer ParentBoxWindowContainer
		{
			get
			{
				return _parentBoxWindowContainer;
			}
			internal set
			{
				_parentBoxWindowContainer = value;
				this.UpdateVisibility();
			}
		}

		/// <summary>
		/// Activates auto-hide feature for the given Box.
		/// </summary>
		public void AutoHideBox(Box box)
		{
			if (box != null)
			{
				if ((_parentBoxWindowContainer != null) && (this.AutoHiddenBoxes != null))
				{
					double areaSize = Double.NaN;
					double lineSize = Double.NaN;
					double boxSize = Double.NaN;
					int lineIndex = 0;
					int boxIndex = 0;
					int? lineID = null;
					int? boxID = null;

					// Get sizes of BoxArea, BoxLine and Box itself to store it in layout configuration
					switch (this.Direction)
					{
						case BoxItemDockDirection.DockTop:
							areaSize = _parentBoxWindowContainer.TopBoxHeight ?? Double.NaN;
							break;
						case BoxItemDockDirection.DockBottom:
							areaSize = _parentBoxWindowContainer.BottomBoxHeight ?? Double.NaN;
							break;
						case BoxItemDockDirection.DockLeft:
							areaSize = _parentBoxWindowContainer.LeftBoxWidth ?? Double.NaN;
							break;
						case BoxItemDockDirection.DockRight:
							areaSize = _parentBoxWindowContainer.RightBoxWidth ?? Double.NaN;
							break;
						default:
							break;
					}

					BoxLine boxLine = box.ParentBoxLine;
					if (boxLine != null)
					{
						lineIndex = this.BoxLines.IndexOf(boxLine);
						lineSize = this.GetRowHeight(lineIndex, true);
						lineID = boxLine.ID;

						boxIndex = boxLine.Boxes.IndexOf(box);
						boxSize = boxLine.GetColumnWidth(boxIndex, true);
						boxID = box.ID;
					}

					// Now create a new AutoHiddenBox and move all BoxItems to it
					AutoHiddenBox autoHiddenBox = new AutoHiddenBox(this);
					this.AutoHiddenBoxes.Add(autoHiddenBox);
					List<BoxItem> tempBoxItemList = new List<BoxItem>(box.Items);
					for (int i = 0; i < tempBoxItemList.Count; i++)
					{
						BoxItem boxItem = tempBoxItemList[i];

						// Store the sizes for later restore
						boxItem.AutoHideStoredBoxAreaSize = areaSize;
						boxItem.AutoHideStoredBoxLineIndex = lineIndex;
						boxItem.AutoHideStoredBoxLineSize = lineSize;
						boxItem.AutoHideStoredBoxIndex = boxIndex;
						boxItem.AutoHideStoredBoxSize = boxSize;
						boxItem.AutoHideStoredBoxLineID = lineID;
						boxItem.AutoHideStoredBoxID = boxID;
						boxItem.AutoHideStoredItemIndex = i;

						if (boxItem.ParentBox != null)
						{
							boxItem.ParentBox.Items.Remove(boxItem);
						}
						autoHiddenBox.Items.Add(boxItem);

						// Save current layout settings (so they can be restored later)
						_parentBoxWindowContainer.SaveLayout(boxItem);
					}
				}
			}
		}

		/// <summary>
		/// Restores an <see cref="AWZhome.awzBoxes.AutoHiddenBox"/> creating
		/// a new <see cref="AWZhome.awzBoxes.Box"/> again.
		/// </summary>
		public void RestoreAutoHiddenBox(AutoHiddenBox box)
		{
			if ((box != null) && (_parentBoxWindowContainer != null) && (this.AutoHiddenBoxes != null))
			{
				if (box.Items.Count > 0)
				{
					// Remove the AutoHiddenBox from list and restore its BoxItems from saved layout settings
					List<BoxItem> tempBoxItemList = new List<BoxItem>(box.Items);
					foreach (BoxItem boxItem in tempBoxItemList)
					{
						_parentBoxWindowContainer.AddBoxItem(boxItem, true);
					}
					// Destroy the AutoHiddenBox
					box.Items.Clear();
					tempBoxItemList.Clear();
				}

				this.AutoHiddenBoxes.Remove(box);
			}
		}

		private void BoxLines_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			// Redirect all changes on Children collection to SplitterRowGrid
			switch (e.Action)
			{
				case NotifyCollectionChangedAction.Add:
					foreach (FrameworkElement elem in e.NewItems)
					{
						this.splittedGrid.Children.Insert(e.NewStartingIndex, elem);
						BoxLine boxLine = elem as BoxLine;
						if (boxLine != null)
						{
							boxLine.ParentBoxArea = this;
							boxLine.IsSideBar = this.IsSideBar;
							if (!boxLine.ID.HasValue)
							{
								if (this.BoxLines.Count > 0)
								{
									boxLine.ID = this.BoxLines.Max(b => b.ID.HasValue ? b.ID.Value : -1) + 1;
								}
								else
								{
									boxLine.ID = 0;
								}
							}
						}
					}
					break;

				case NotifyCollectionChangedAction.Move:
					break;

				case NotifyCollectionChangedAction.Remove:
					foreach (FrameworkElement elem in e.OldItems)
					{
						this.splittedGrid.Children.Remove(elem);
						BoxLine line = elem as BoxLine;
						if (line != null)
						{
							line.ParentBoxArea = null;
							if ((this.BoxLines.Count > 0) && (this.BoxLines[0].Boxes.Count > 0))
							{
								Box box = this.BoxLines[0].Boxes[0];
								if (box.Items.Count > 0)
								{
									this.ActiveBoxItem = box.Items[0];
								}
							}
							else
							{
								this.ActiveBoxItem = null;
							}
							((BoxLine) elem).ID = null;
						}
					}
					break;

				case NotifyCollectionChangedAction.Replace:
					foreach (FrameworkElement elem in e.OldItems)
					{
						this.splittedGrid.Children.Remove(elem);
						BoxLine line = elem as BoxLine;
						if (line != null)
						{
							line.ParentBoxArea = null;
							if ((this.BoxLines.Count > 0) && (this.BoxLines[0].Boxes.Count > 0))
							{
								Box box = this.BoxLines[0].Boxes[0];
								if (box.Items.Count > 0)
								{
									this.ActiveBoxItem = box.Items[0];
								}
							}
							else
							{
								this.ActiveBoxItem = null;
							}
							((BoxLine) elem).ID = null;
						}
					}
					foreach (FrameworkElement elem in e.NewItems)
					{
						this.splittedGrid.Children.Add(elem);
						if (elem is BoxLine)
						{
							BoxLine boxLine = elem as BoxLine;
							boxLine.ParentBoxArea = this;
							if (boxLine != null)
							{
								boxLine.ParentBoxArea = this;
								boxLine.IsSideBar = this.IsSideBar;
								if (!boxLine.ID.HasValue)
								{
									if (this.BoxLines.Count > 0)
									{
										boxLine.ID = this.BoxLines.Max(b => b.ID.HasValue ? b.ID.Value : -1) + 1;
									}
									else
									{
										boxLine.ID = 0;
									}
								}
							}
						}
					}
					break;

				case NotifyCollectionChangedAction.Reset:
					break;

				default:
					break;
			}

			this.UpdateVisibility();
		}

		/// <summary>
		/// Updates the control's visibility according to the number of <see cref="AWZhome.awzBoxes.BoxLine"/>
		/// objects in it.
		/// </summary>
		private void UpdateVisibility()
		{
			if (this.IsSideBar)
			{
				// If there are no BoxLines in here, collapse this BoxArea
				if (this.BoxLines.Count == 0)
				{
					this.Visibility = Visibility.Collapsed;
				}
				else
				{
					this.Visibility = Visibility.Visible;
				}
				this.splittedGrid.Visibility = Visibility.Visible;
			}
			else
			{
				this.Visibility = Visibility.Visible;
				if (this.BoxLines.Count == 0)
				{
					this.splittedGrid.Visibility = Visibility.Collapsed;
				}
				else
				{
					this.splittedGrid.Visibility = Visibility.Visible;
				}
			}
		}
	}
}
